/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.google.code.twiddling.core.io;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import com.google.code.twiddling.core.bootstrap.TwiddlingShell;
import com.google.code.twiddling.core.context.Environment;
import com.google.code.twiddling.core.layout.LayoutManager;

/**
 * This class implements helper functions for the console to
 * 
 * <li>get a Command from the Terminal thru the input stream</li> <li>get a
 * Command String from the Console thru the input stream</li> <li>get user input
 * from the console</li> <li>output text to the Terminal thru the Console output
 * stream</li>
 * 
 * The input part of this class is not supposed to be accessed concurrently. The
 * output part however can.
 * 
 * @author <a href="mailto:howard.gao@gmail.com">Howard Gao</a>
 */
public class BasicIOManager extends SystemIO implements IOManager {

	private UserInputListener listener;
	private CommandListener cmdListener;
	private InputBuffer buffer;
	private UserInputThread inputThread;
	private ProcessThread processThread;
	private boolean pageMode;
	private boolean started = false;
	private Environment environemnt;
	private Reader reader;

	public BasicIOManager(Environment environment) {
		super();
		this.environemnt = environment;
		this.reader = new InputStreamReader(inputStream);
		buffer = new InputBuffer();
		pageMode = false;
	}

	/**
	 * start the IO
	 */
	public void start() {
		if (started)
			return;
		inputThread = new UserInputThread();
		inputThread.start();
		processThread = new ProcessThread();
		processThread.start();
		showBanner();
		showProfile();
		started = true;
	}

	private void showBanner() {
		println("");
		println("");
		println("");
		println(TwiddlingShell.BANNER);
		println(TwiddlingShell.VERSION);
		println(TwiddlingShell.HOMEPAGE);
		// FIXME: the command input will not be shown if using AnsiRender.
		/*
		 * LayoutManager.getBannerLayout().setBanner(TwiddlingShell.BANNER)
		 * .setVersion(TwiddlingShell.VERSION)
		 * .setHomepage(TwiddlingShell.HOMEPAGE) .print(this.out);
		 */
		println("");
		out.flush();
	}

	/*
	 * stop the IO
	 */
	public void stop() {
		if (!started)
			return;
		processThread.shutdown();
		try {
			processThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		inputThread.shutdown();
		try {
			inputThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		inputThread = null;
		processThread = null;
		buffer.reset();
		started = false;
	}

	private void showProfile() {
		out.print(environemnt.getProfile().toString());
		out.flush();
	}

	private void newInputChar(Character c) {
		if (listener != null) {
			listener.onNewInput(c);
		} else {
			buffer.newInputChar(c);
		}
	}

	/**
	 * get the user's raw input string and returns. this helper reads the
	 * terminal input stream until a carriage return is read.
	 * 
	 * @return the full string.
	 */
	public String getCommandString(long timeout) {
		return buffer.getCommandString(timeout);
	}

	/**
	 * read one char from the terminal input and returns immediately.
	 */
	public char getUserInput() {
		return ' ';
	}

	/**
	 * register an input listener to respond to the user input.
	 */
	public void registerUserInputListener(UserInputListener inputListener) {
		listener = inputListener;
	}

	/**
	 * TODO: Need to re-work, at least some consolidation with
	 * UserInputListener.
	 * 
	 * @param listener
	 */
	public void registerCommandListener(CommandListener listener) {
		cmdListener = listener;
	}

	public void println(String string) {
		out.println(string);
		out.flush();
	}

	public void print(String string) {
		out.print(string);
		out.flush();
	}

	private class ProcessThread extends Thread {

		private BasicIOManager owner = BasicIOManager.this;
		private boolean shutdown;

		public ProcessThread() {
			super("ProcessThread");
			shutdown = false;
		}

		public void shutdown() {
			shutdown = true;
		}

		public void run() {
			while (!shutdown) {
				String cmd = owner.getCommandString(50);
				if (cmd != null) {
					cmd = cmd.trim();
					owner.cmdListener.process(cmd);
					if (!"exit".equals(cmd) && !"bye".equals(cmd)) {
						owner.showProfile();
					}
				}
			}
		}
	}

	private class UserInputThread extends Thread {

		private BasicIOManager owner = BasicIOManager.this;
		private volatile boolean shutdown;

		public UserInputThread() {
			super("UserInputThread");
			shutdown = false;
		}

		public void shutdown() {
			shutdown = true;
		}

		public void run() {
			while (!shutdown) {
				try {
					if (!owner.buffer.isAlert()) {
						int inChar = owner.getReader().read();
						owner.newInputChar(Character.valueOf((char) inChar));
					} else {
						try {
							Thread.sleep(20);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public IO getIO() {
		return this;
	}

	public Reader getReader() {
		return reader;
	}

}
